/*
 * Copyright (c) 2016. 恒昌互联网运营中心.
 */
package com.credithc.eagleeye.plugins.feign;
import com.credithc.eagleeye.agent.factory.EagleEyeAgentFactory;
import com.credithc.eagleeye.agent.util.HostUtil;
import com.credithc.eagleeye.constants.RpcTypeEnum;
import com.credithc.eagleeye.exception.EagleEyeIgnoreException;
import com.credithc.eagleeye.thread.EagleEyeThreadContext;
import com.credithc.eagleeye.thread.TraceInfo;
import com.credithc.eagleeye.util.MixUtil;
import com.credithc.eagleeye.util.ThrowableToStringArray;
import com.credithc.eagleeye.vo.RpcTraceInfoVo;
import feign.Request;
import feign.Response;
import feign.Util;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;
/**
 * 鹰眼切面
 */
@SuppressWarnings("Duplicates")
@Aspect
@Deprecated
public class EagleEyeFeignClientAspect {
    private transient static final Logger logger = LoggerFactory.getLogger(EagleEyeFeignClientAspect.class);

    @Pointcut("execution(* org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(..))")
    public void pointcut() {
    }


    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.debug("EagleEyeFeignClientAspect around start...");
        Object resultObject = null;
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length == 2) {
            Object arg0 = args[0];
            if (arg0 instanceof Request) {
                Request request = (Request) arg0;
                TraceInfo traceInfo = EagleEyeThreadContext.getTraceInfo();
                if (traceInfo == null) {
                    traceInfo = new TraceInfo();
                    EagleEyeThreadContext.putTraceInfo(traceInfo);
                    MDC.put("traceId", traceInfo.getTraceId());
                    MDC.put("rpcId", traceInfo.getRpcId());
                } else {
                    traceInfo.addSequenceNo();
                }
                /*将鹰眼traceId放到feignClient中**/
                Field headersField = Request.class.getDeclaredField("headers");
                //将字段的访问权限设为true：即去除private修饰符的影响
                headersField.setAccessible(true);
                //去除final修饰符的影响，将字段设为可修改的
                Field modifiersField = Field.class.getDeclaredField("modifiers");
                modifiersField.setAccessible(true);
                modifiersField.setInt(headersField, headersField.getModifiers() & ~Modifier.FINAL);

                Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
                List<String> traceId = new LinkedList<>();
                traceId.add(traceInfo.getTraceId());
                headers.put("hc-traceId", traceId);
                List<String> rpcId = new LinkedList<>();
                rpcId.add(traceInfo.getRpcId());
                headers.put("hc-rpcId", rpcId);
                headers.putAll(request.headers());
                headersField.set(request, Collections.unmodifiableMap(headers));

                //初始化鹰眼
                long beginTime = System.currentTimeMillis();
                RpcTraceInfoVo rpcTraceInfoVo = new RpcTraceInfoVo();

                try {
                    rpcTraceInfoVo.setRequestDateTime(MixUtil.ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(new Date()));
                    rpcTraceInfoVo.setTraceId(traceInfo.getTraceId());
                    rpcTraceInfoVo.setRpcId(traceInfo.getRpcId());
                    rpcTraceInfoVo.setRpcType(RpcTypeEnum.FEIGN.getRpcName());
                    URI asUri = URI.create(request.url());
                    String serviceName = asUri.getHost();
                    String methodName = StringUtils.removeStart(asUri.getPath(),"/");
                    rpcTraceInfoVo.setServiceCategory("feign");
                    rpcTraceInfoVo.setServiceName(serviceName);
                    rpcTraceInfoVo.setMethodName(methodName);
                    rpcTraceInfoVo.setClientHost(HostUtil.getIp());
                    rpcTraceInfoVo.setServerHost(request.url());

                    //请求的参数
                    boolean enableMvcParameter = EagleEyeAgentFactory.getInstance().getEagleEyeAgentConfig().isEnableMvcParameter();
                    if(enableMvcParameter ) {
                        if(request.body() != null) {
                            String bodyText = request.charset() != null ? new String(request.body(), request.charset()) : null;
                            rpcTraceInfoVo.setRequestJson(bodyText != null ? bodyText : "Binary data");
                            rpcTraceInfoVo.setRequestSize(request.body().length);
                        }else{
                            rpcTraceInfoVo.setRequestJson(request.method()+" "+request.url());
                        }
                    }
                    rpcTraceInfoVo.setUserInfo(traceInfo.getUserInfo());

                    //执行调用
                    resultObject = joinPoint.proceed();
                    rpcTraceInfoVo.setRunTime(System.currentTimeMillis()-beginTime);
                    Response response = (Response) resultObject;
                    int status = response.status();
                    if(status==200) {
                        rpcTraceInfoVo.setResult("OK");
                    }else{
                        rpcTraceInfoVo.setResult("ERROR");
                    }
                    //返回结果
                    if(enableMvcParameter) {
                        Response.Body body = response.body();
                        int bodyLength = 0;
                        if (response.body() != null && !(status == 204 || status == 205)) {
                            // HTTP 204 No Content "...response MUST NOT include a message-body"
                            // HTTP 205 Reset Content "...response MUST NOT include an entity"
                            byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                            bodyLength = bodyData.length;
                            if (bodyLength > 0) {
                                rpcTraceInfoVo.setResponseJson(decodeOrDefault(bodyData, UTF_8, "Binary data"));
                            }
                            //重新设置body，防止无法以流的方式二次读取
                            resultObject=response.toBuilder().body(bodyData).build();
                        } else {
                            rpcTraceInfoVo.setResponseJson("");
                        }
                        rpcTraceInfoVo.setResponseSize(bodyLength);
                    }
                }catch(EagleEyeIgnoreException ex){
                    rpcTraceInfoVo.setResult("OK");
                }catch(Exception ex){
                    //ERROR
                    rpcTraceInfoVo.setRunTime(System.currentTimeMillis()-beginTime);
                    rpcTraceInfoVo.setResult("ERROR");
                    //异常的结果
                    String traceStr[] = ThrowableToStringArray.convert(ex);
                    StringBuilder builder = new StringBuilder();
                    for(String trace : traceStr) {
                        builder.append(trace);
                        builder.append("\n");
                    }
                    rpcTraceInfoVo.setResponseJson(builder.toString());
                    throw ex;
                }finally {
                    EagleEyeAgentFactory.getInstance().send2EagleEye(rpcTraceInfoVo);
                }
            }
        }
        logger.debug("EagleEyeFeignClientAspect around stop...");
        return resultObject;
    }

}
